[cognitiveservices] Unblock managed compute creation - fix misleading 202 / AuthorizationFailed errors #48096
Conversation
…lerate read-after-write 404 Override begin_create_or_update (sync + async) to accept the service's HTTP 202 and track the LRO by polling the compute resource (BodyContentPolling) instead of the computeOperations status endpoint that needs computeOperations/read many callers lack. Tolerates the ~15-20s read-after-write 404 window, blocks until terminal, and surfaces the compute's own provisioning error. Adds 14 unit tests; bumps to 15.0.0b4.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
…, no computeOperations) Poll provisioningState from the list API instead of a resource GET: a GET on a just-created compute returns 404 'Cluster not found' for an extended period while it provisions, whereas list reflects state from the moment the create is accepted. Keeps the 202 accept, blocks until terminal via a custom _ComputeListPolling, surfaces the compute's real error on failure, and preserves the public overloads, api-version validation and continuation_token resume path. Updates 22 unit tests and the CHANGELOG.
|
Azure Pipelines: Successfully started running 1 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
…tched ComputesOperations
…idate continuation_token - Apply caller-supplied cls to the final resource in the list-based poller. - Preserve terminal Canceled status instead of collapsing it to Failed. - Encode/decode continuation_token so resume uses the token as source of truth and raises on a malformed token. - Add sync+async tests for all three (30 tests total).
|
We will discuss about this PR in e-mail. |
Co-authored-by: Yuchao Yan <yuchaoyan@microsoft.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (6)
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/operations/_patch.py:473
- The continuation token is decoded as this new list-poller format before the caller-supplied
PollingMethodis selected. Tokens are owned by their polling method—LROPoller.from_continuation_tokendelegates topolling_method.from_continuation_token—so this rejects every custom poller's token and breaks the existing resume contract. Decode the scope only for the default list poller and otherwise pass the token through untouched.
resource_group_name, account_name, compute_name = _decode_continuation_token(cont_token)
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/aio/operations/_patch.py:387
- The continuation token is decoded as this new list-poller format before the caller-supplied
AsyncPollingMethodis selected.AsyncLROPoller.from_continuation_tokendelegates decoding to the selected polling method, so this rejects custom async poller tokens and regresses resumability. Decode only when using the default list poller; custom-method tokens must pass through unchanged.
resource_group_name, account_name, compute_name = _decode_continuation_token(cont_token)
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/operations/_patch.py:223
- Passing
Noneas the firstclsargument does not preserve the generated hook contract: callers may inspect the finalPipelineResponsefor status, headers, or context. The added test only validates the deserialized argument, so such callbacks now fail with an attribute error. Capture the response associated with the final list read and pass that response tocls.
if self._resource is not None and self._cls is not None:
return self._cls(None, self._resource, {})
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/aio/operations/_patch.py:137
- Passing
Noneas the firstclsargument breaks the generated async hook contract for callbacks that inspect the finalPipelineResponse. The test only checks the deserialized value, masking this regression. Retain the response from the final async list page and provide it tocls.
if self._resource is not None and self._cls is not None:
return self._cls(None, self._resource, {})
sdk/cognitiveservices/azure-mgmt-cognitiveservices/api.md:1182
- The PR states that the public API surface is unchanged, but the regenerated snapshot now publishes a private
_ComputesOperationsGeneratedbase class. This is an observable inheritance/API change rather than the prior base-free declaration. Keep the implementation base out of the public declaration (or obtain API approval for the change) and regenerate the snapshot.
class azure.mgmt.cognitiveservices.aio.operations.ComputesOperations(_ComputesOperationsGenerated):
sdk/cognitiveservices/azure-mgmt-cognitiveservices/api.md:9526
- The PR claims the public API is unchanged, but this snapshot exposes the new private
_ComputesOperationsGeneratedbase in the sync public class. That changes the documented inheritance surface. Avoid replacing the public class with a subclass (or obtain API approval for this change), then regenerate the API artifacts.
class azure.mgmt.cognitiveservices.operations.ComputesOperations(_ComputesOperationsGenerated):
…5.0.0b3 to 15.0.0b4 Adopts the SDK release that includes Swarup's fix (Azure/azure-sdk-for-python#48096) for compute create polling. End-to-end validated in a local editable-install of 15.0.0b4: request accepted, polling correctly hits the LIST endpoint, real provisioning errors surface (no more misleading AuthorizationFailed on computeOperations/read). CI will fail on dependency resolution until 15.0.0b4 is published to PyPI (same pre-publish precedent as PR Azure#33132). Once the SDK is on PyPI, this CLI PR is ready to merge without re-review.
|
Good job, @swarupecenits ! |
|
Also tested this PR from CLI: Azure/azure-cli#33759 |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/operations/_patch.py:223
- The generated
clscontract requires a realPipelineResponseas its first argument (_operations.py:43, and the original LRO callback passes the final response at lines 20543-20547). PassingNonebreaks valid callbacks that inspectpipeline_response.http_responsewhenever default list polling completes. Preserve an actual response from the list read-back (or otherwise retain the callback contract) instead of substitutingNone.
return self._cls(None, self._resource, {})
sdk/cognitiveservices/azure-mgmt-cognitiveservices/azure/mgmt/cognitiveservices/aio/operations/_patch.py:137
- The async generated
clscontract also requires a realPipelineResponseas its first argument. PassingNonemakes supported callbacks that inspectpipeline_response.http_responsefail after polling succeeds. Capture and pass the response associated with the final list read-back rather than weakening the public callback contract.
return self._cls(None, self._resource, {})
sdk/cognitiveservices/azure-mgmt-cognitiveservices/api.md:1182
- This snapshot shows that the async public API surface did change:
ComputesOperationsnow exposes the private_ComputesOperationsGeneratedbase, contrary to the PR's unchanged-surface claim. Adjust the customization so the private implementation base is not part of the public class signature, then regenerate the API snapshot and metadata.
class azure.mgmt.cognitiveservices.aio.operations.ComputesOperations(_ComputesOperationsGenerated):
sdk/cognitiveservices/azure-mgmt-cognitiveservices/api.md:9526
- The sync API snapshot likewise exposes
_ComputesOperationsGeneratedas a new public base class, so the public surface is not unchanged as described. Avoid leaking the private implementation base throughComputesOperations, and regenerate the API artifacts after correcting the class customization.
class azure.mgmt.cognitiveservices.operations.ComputesOperations(_ComputesOperationsGenerated):
Description
Fixes the misleading errors users hit when creating a Cognitive Services managed compute via
ComputesOperations.begin_create_or_update(surfaced throughaz cognitiveservices account compute create).The generated SDK failed asynchronous compute creation in two ways:
202 Accepted. The service returns HTTP202for the async create, but thegenerated
_create_or_update_initialonly accepts200/201, so it raisedOperation returned an invalid status 'Accepted'.Azure-AsyncOperationheaderto
.../locations/{location}/computeOperations/{operationId}, which requiresMicrosoft.CognitiveServices/locations/computeOperations/read. Callers allowed to create a computebut not granted that read permission saw a misleading
AuthorizationFailedeven though the createsucceeded.
What this change does
Overrides
ComputesOperations.begin_create_or_update(sync + async, in_patch.py) to:202in addition to200/201(in a minimal_create_or_update_initialoverride that isotherwise identical to the generated method).
provisioningStatefrom thelistAPI via a customblocking poller (
_ComputeListPolling/_AsyncComputeListPolling), matching the compute by name.This needs only
computes/read(which callers already have) and never touches thecomputeOperationsoperation-status endpoint.properties.errors(e.g. the quota message) instead of a genericOperation returned an invalid status 'OK'.@overloads, the@api_version_validationguard, the
polling=Falseescape hatch, and thecontinuation_tokenresume path (which does notre-issue the create PUT). Genuine non-2xx create failures still propagate.
Why
listand not a resourcegetThe compute create is asynchronous, and a
GETon the just-created compute returns404 "Cluster not found"for an extended, variable period while the cluster backend materializes — longenough that a
get-based poller wrongly fails a create that is actually succeeding. The compute does,however, appear in the
listresponse with itsprovisioningStatefrom the moment the create isaccepted. Polling
listtherefore reflects the true state throughout provisioning. This also matches thework item's guidance to "rely on the list API for status polling of compute creation."
Only private helpers were added (
_ComputeListPolling,_AsyncComputeListPolling,_provisioning_error,_state_of,_TERMINAL_FAILED_STATES,_TERMINAL_SUCCESS_STATES); the public API surface isunchanged (verified: the three public overloads are preserved and api-version validation still fires).
Notes
_patch.pyso the code generator does not overwrite it.
_patch.pyre-implements the initial request send only because the generated_create_or_update_initialrejects the
202; it is kept intentionally close to the generated code.poller.result()semantics) rather than returning early,and surfaces provisioning failures - so callers get correct results, not a premature "Accepted".
15.0.0b3→15.0.0b4; CHANGELOG updated.Testing
tests/test_computes_operations_patch.py+..._async.py, 11 sync + 11 async):accept 202 · reads status via
list, nevercomputeOperations· blocks until terminal · tolerates thecompute being briefly absent from
listthen appearing · gives up (bounded) if it never appears ·surfaces the real failure reason · propagates non-2xx ·
polling=False· public overloads preserved ·old api-version rejected ·
continuation_tokendoes not re-send the create. All pass;black+pyflakesclean.jayant-westcentralus,Standard_D64_v3):provisioningStatefromlist(never a404), and returned the compute withprovisioningState: Succeeded.real error (
ClusterCoreQuotaReachedquota message) instead of a generic status error.AuthorizationFailed/invalid status 'Accepted'occurs.All SDK Contribution checklist:
General Guidelines and Best Practices
Testing Guidelines
Verification
**Unit tests - all passing. **
test_computes_operations_patch.py(sync) andtest_computes_operations_patch_async.py(async) cover accepting the async202, polling the compute resource instead ofcomputeOperations, blocking until terminal state, tolerating the read-after-write404window, surfacing the real provisioning failure, propagating non-2xx errors, and thepolling=Falseescape hatch.Live end-to-end success.
begin_create_or_update(...).result()accepts the async 202, tracksstatus via the
listAPI (STEP 2: computeOperations never called), blocks through ~33 min ofprovisioning, and returns the created
Standard_D64_v3compute withprovisioningState: SucceededAuthorizationFailed, noinvalid status 'Accepted', and none of the404 "Cluster not found"that a
get-based poller hits during creation.